home *** CD-ROM | disk | FTP | other *** search
- /* -------------------------------------------- */
- /* 3D Demonstration (DRAFT) */
- /* as told in Megamax C V2.1 */
- /* by */
- /* M H Miller ... 30 March 1986 */
- /* wire frame 10 April 1986 */
- /* rotations 15 April 1986 */
- /* visibility 1 May 1986 */
- /* touchup 3 May 1986 */
- /* add 1st order lighting 8 May 1986 */
-
- /* This is an 'application' which eventually will take its proper place among a set of exercises
- * whose purpose is enhancing understanding of C programming (Megamax C is the particular
- * vehicle used), and in facilitating oozing through Inside Macintosh. In most of these
- * exercises the methodology is to explore a number of Inside Macintosh functions and
- * procedures, observe their visual effects (if any), study their syntax, and in general
- * evaluate their use and usfulness in the Macintosh interface. Modifications, enhancements,
- * non_commercial uses of this material, and tactful comments are invited, indeed encouraged.
- *
- * In studying the exercise(s) keep in mind ( for my sake at least) that the principal intention
- * is not to optimize the operation(s) performed but rather to examine Macintosh operations
- * and C programming. With that view what you don't care for should be considered an
- * objective illustration of what not to do.
- */
-
- /* --------------------------------------------------------
- This exercise involves computation of the isometric projection of a solid and rotation
- of that solid about an axis. The character of the solid is constrained so that particular
- circumstances requiring extended computation and manipulation are avoided. Specifically the
- solid must have plane faces which are closed polygons. The solid must be 'convex', i.e., the
- angle between outward normals to two contiguous faces must be greater than 90 degrees; this
- eliminates solids with 'holes', indentations, or 'pimples'. For a convex solid a face either is
- visible in its entirety or it isn't; there is no 'shadowing' of one face by others. Rotations of
- the solid are about a single (coordinate) axis at a time for illustrative simplicity, although
- generalizing is not all that difficult or involved. For each rotated position an equiangular
- isometric projection is computed. In addition the orientation of each face of a projection is
- computed to determine if the outward normal of a projected face points into or out of the
- screen; a face is visible looking into the screen if its outward normal points out of the screen.
- A dynamic presentation of the rotating solid is drawn, optionally either as a wire frame or as
- a solid. Additional variations are planned for addition now and then.
-
- NOTE 1: The nature of the data structures used is of some importance. However no great emphasis
- was placed on optimizing these in this illustration. Consider this illustration to be an illustration
- and not a 'how to' prescription. Nevertheless the program is written to allow easy redefinition of
- the object rotated, within the simplifying constraints noted.
-
- NOTE 2: This is a 'brute force' version to provide a reference against which to evaluate the
- efficacy of measures to reduce flickering and image discontinuity. Some modifications
- to be added sooner or later are bitmap switching, assembly language interventions, synchronizing
- to the blanking interrupts, and cleaning the screen as a VBL task.
- -------------------------------------------------------- */
-
- #include <stdio.h>
- #include <event.h>
- #include <qdvars.h>
- #include <control.h>
- #include <os.h>
- #include <fmath.h>
-
- typedef int void; /* Thoroughly Modern Miller */
-
- #define X 0 /* Axis references */
- #define Y 1
- #define Z 2
-
- /* For simplicity the object to be drawn is predefined. The specification is for the 'base'
- position from which all rotated positions are computed. Within the constraints on the
- type of object allowed a new object can be redefined by redefining the parameters as
- described or readily inferred. The following three parameters are used to set
- computation loop limits; redefine to suit.
- */
- #define NMBR_FACES 9
- #define NMBR_VERTICES 9
- #define NMBR_SETS 20 /* rotation angle step == 360/NMBR_SETS*/
-
-
- /* Object vertex coordinates (relative to vertex 0). This example is for a regular
- trapazoidal parallelpiped. This part of the specification only partially defines the
- solid; still to come is specification of the face edges, i.e., the connections between
- vertices which define the individual faces. Dimensions are referenced to LX, LY,
- and LZ. Note that rotations will be about coordinate axes for simplicity. However that
- does not constrain the initial orientation of the object; it can be 'tilted' as desired.
- */
- #define LX 80
- #define LY 40
- #define LZ 60
-
- /* #####################################
- 'Plain Jane' initial orientation (not used)
-
- int vertex[ NMBR_VERTICES ][3] = { { 0,0,0 },
- { 0, 0,LZ },
- { LX, 0,LZ },
- { LX, 0, 0 },
- {3*LX/4,LY, LZ/4},
- { 3*LX/4,LY,3*LZ/4 },
- {LX/4,LY,3*LZ/4},
- {LX/4,LY,LZ/4},
- {LX/2,-LY/2,LZ/2}
- };
-
- ##################################### */
-
-
- /* 'Centerfold' (more exposed) initial position; scale not the same as for Plain Jane. This
- is the specification used for the program.
- */
-
- int vertex[ NMBR_VERTICES ][3] = { { 0,0,LZ/2 },
- { LX/2, 0,LZ },
- { LX, 0, LZ/2 },
- { LX/2,0,0 },
- {LX/2,LY, LZ/4},
- { 3*LX/4,LY,LZ/2 },
- {LX/2,LY,3*LZ/4},
- {LX/4,LY,LZ/2},
- {LX/2,-LY/2,LZ/2}
- };
-
- /* A face is defined its edges; in turn these are defined by a set of vertices. The vertex list
- is determined as follows:
- a) Imagine the outward normal to the surface to be drawn.
- b) Imagine your right hand curled around the normal, thumb pointing outward from the
- face along the normal.
- c) Follow the curl of your fingers in listing the vertices forming the face edges. Note
- that the last vertex listed is the same as the first one; any of the face vertices may
- be 'first' in the list.
- The number of vertices need not be the same for all faces. Note again that faces are required
- to be planar polygons. Violation of this constraint generally shows up as overlapping faces as
- the object rotates.
- The following 'face' specification is for the trapazoidal pyramid. Per standard C rules any
- array elements not specified will be set to 0, although this is not important since they are
- not used at all.
- */
- int face[NMBR_FACES][5] = { {0,1,6,7,0},
- {1,2,5,6,1},
- {2,3,4,5,2},
- {7,4,3,0,7},
- {7,6,5,4,7},
- {0,3,8,0},
- {8,1,0,8},
- {8,2,1,8},
- {8,3,2,8}
-
- };
-
- /* Define the coordinates xr,yr of the center of rotation/origin (this will be taken to be
- somewhere in the plane of the screen), and the offset of vertex 0 from the center of rotation
- (dxo, dyo, dzo). Thus the absolute location of, say the X-coordinate of vertex 4, is
- xr+dxo+vertex[4][X]. See below for the computation of projection coordinates.
- */
- int xr = 200, yr = 160, zr = 0, dxo = 50, dyo = 30, dzo = -50;
-
- /* For each face a polygon will be computed (later) for the projection of that face */
- polyhandle face_poly[NMBR_SETS][NMBR_FACES];
-
- /* For each face, and in each set the direction of the normal to a projected face is computed
- to determine if that face is visible or not. In addition the illumination ( yes or no) of a face
- from a source to the left and up is determined.
- */
- short visible[NMBR_SETS][ NMBR_FACES];
- short light[NMBR_SETS][ NMBR_FACES];
-
- short drawflag; /* Switch between drawing types. SOLID illustrates the use of the 'visibility'
- computation, i.e., only 'visible' faces are drawn. SOLID_LIGHT adds
- first-order illumination.
- */
- #define SOLID_LIGHT 0
- #define SOLID 1
- #define WIRE_FRAME 2
-
- rect option_rect, display_rect; /* Housekeeping */
-
- main()
- { eventrecord the_event;
- point mousepoint;
- grafptr the_port;
- int i,j;
- register int this_set,next_set;
- rect temp_rect;
-
- initgraf(&theport);
- initfonts();
- initwindows();
- initcursor();
-
- /* Use the window manager port (desktop) directly. Open up the clipregion to include the
- menu bar and clear the screen. Define a housekeeping rectangle for erasing the screen.
- */
- getwmgrport(&the_port);
- setport(the_port);
- cliprect(&(the_port->portrect));
- eraserect(&(the_port->portrect));
- setorigin(0,0);
- setrect(&display_rect,0,21,512,342);
-
- /* Startup with SOLID_LIGHT, X-axis rotation */
- compute_rotation_data(X);
- drawflag = SOLID_LIGHT;
-
-
- for(;;)
- { getnextevent(everyevent,&the_event);
- if ( the_event.what == mousedown )
- { getmouse(&mousepoint);
-
- if (ptinrect(&mousepoint,&option_rect) )
- { switch (i = mousepoint.a.h/15)
- { case 0: /* Q for QUIT */
- exit(0);
- break;
-
- case 1: case 2: case 3: /* Rotate X, Y, Z respectively */
- compute_rotation_data(i-1);
- break;
-
- case 4: /* SOLID, SOLID_LIGHT, WIRE_FRAME */
- if (++drawflag == 3) drawflag = 0;
- break;
- }
- } else while(button()) ; /* Freeze frame */
- }
-
- rotate_object(); /* Take a step */
- }
- return;
- }
-
- void compute_rotation_data(axis)
- /* The isometric projection is an equiangular proection on the plane (screen). The y-axis
- coordinate of a point is drawn vertically (+ down) from the origin. The x-axis position
- is along a line drawn 30 degrees down from horizontal, while the z-axis is drawn along a
- line elevated 30 degrees above the horizontal. The net result is that the screen position
- relative to the origin of the point x, y, z is (in screen position coordinates)
- (x,y) = (x+z)*cos(30), y+(x-z)*sin(30)
- This procedure computes the rotated position of the vertices, computes whether a
- projected face is 'visible' or not, and defines polygons for each face in each projection.
- The data produced is 'face_poly[this_set][i]' which is the polygon for drawing face#i after
- a rotation of 360*this_set/NMBR_SETS. No indication provided for axis of rotation. In addition
- 'visible[this_set][i]' =1/0 indicates that face is visible/invisible seen looking into the screen.
-
- Note: The only global parameters provided by this illustrative computation are the polygon
- handles and the 'visibility' and 'light' arrays.
- */
-
- int axis;
- { register int this_set,i,j; /* Rotation angle is 360*this_set/NMBR_SETS*/
- int basev[NMBR_VERTICES][3]; /* Vertex coordinates offset from center of rotation */
- int v[NMBR_VERTICES][3]; /* Vertex rotated coordinates */
- int p_vx[NMBR_VERTICES], p_vy[NMBR_VERTICES]; /* Projection coordinates */
- int ax,ay,az,bx,by,bz; /* Vector components for computing direction cosines */
-
- /* Miscellaneous utility variables*/
- rect count_rect;
- int v0,v1,v2;
- float sin_table, cos_table;
- #define D_THETA 6.283185/NMBR_SETS
-
- /* Cf Psych 100 notes */
- setrect(&count_rect,0,21,512,342);
- eraserect(&count_rect);
- textsize(18); textface(bold); textfont(geneva); pennormal();
- moveto(200,150); drawstring("HOLD ON");
- moveto(50,170); drawstring("I'M COMPUTING WHAT I NEED TO KNOW");
-
- /* Compute the rotated position of the vertices. Note that the computation references
- the base vertex definitions for each computation rather than accumlating incremental
- rotations. For real time rotation computations the latter would be appropriate, provided
- regular reinitialization, direct or indirect, avoided error accumulation.
- */
-
- /* Locate vertices relative to center of rotation */
- for(i=0;i<NMBR_VERTICES;++i)
- { basev[i][X] = vertex[i][X] + dxo;
- basev[i][Y] = vertex[i][Y] + dyo;
- basev[i][Z] = vertex[i][Z] + dzo;
- }
-
- /* Angular increment is 360/NMBR_SETS */
- for (this_set=0;this_set<NMBR_SETS;++this_set)
- { /* Convenience variables */
- sin_table = sin( D_THETA*this_set);
- cos_table = cos(D_THETA*this_set);
-
- /* Rotate the vertices. Note that the coordinates are relative to the center of rotation.*/
- for ( i=0;i<NMBR_VERTICES;++i)
- { switch (axis)
- { case Z:
- v[i][X] = basev[i][X]*cos_table - basev[i][Y]*sin_table;
- v[i][Y] = basev[i][X]*sin_table + basev[i][Y]*cos_table;
- v[i][Z] = basev[i][Z];
- break;
- case Y:
- v[i][X] = basev[i][X]*cos_table + basev[i][Z]*sin_table;
- v[i][Y] = basev[i][Y];
- v[i][Z] = -basev[i][X]*sin_table + basev[i][Z]*cos_table;
- break;
- case X:
- v[i][X] = basev[i][X];
- v[i][Y] = basev[i][Y]*sin_table + basev[i][Z]*cos_table;
- v[i][Z] = -basev[i][Y]*cos_table + basev[i][Z]*sin_table;
- break;
- }
- }
-
- /* A source of light is assumed far away and to the right. A face is illuminated if 'sees'
- the light, i.e., if its outward normal is directed to the right. In addition of course
- the face will have to be visible in the projection, as computed later. This illumination
- algorithm does not provided graded lighting as would occur in reality. A face either
- is or is not illuminated.
- Note: See comments below for 'visible' computation for additional pertinent remarks
- not included here.
- */
- for ( i=0;i<NMBR_FACES;++i)
- { v0= face[i][0]; v1= face[i][1]; v2= face[i][2];
- ay = v[v2][Y] - v[v1][Y]; by = v[v0][Y] - v[v1][Y];
- az = v[v2][Z] - v[v1][Z]; bz = v[v0][Z] - v[v1][Z];
-
- light[this_set][i]= (ay*bz- az*by)>0 ?0 :1; /* An illuminated face has a 0 flag. */
- }
-
- /* Compute the isometric positions for each vertex in the current set. The projection is
- * computed relative to the center of rotation as origin and then reset relative to the
- * screen origin.
- */
- for(i=0;i<NMBR_VERTICES;++i)
- { p_vx[i] = xr + .86603*(v[i][X] + v[i][Z]);
- p_vy[i] = yr + v[i][Y] + (v[i][X] - v[i][Z])/2;
- }
-
- /* A face of the projection is 'visible' if it can be seen looking into the screen. To
- * determine visibility use the fact that faces are defined by the array face[][4]
- * which provides the clockwise sequence of vertices defining the face. The
- * procedure is:
- * a) Select three consecutive vertices #0,#1,#2 from the face definition vector
- * to define two projected face edges. The first three are used. Note that the
- * face definition vector lists vertices circulating CCW about outward directed normal.
- * b) Define edge#1 as fron vertex #1 to vertex #2; call this edge A.
- * c) Define edge#2 as fron vertex #1 to vertex #0; call this edge B.
- * d) The normal is A x B= Ax*By - Ay*Bx; if this is negative (out of the screen) the
- * face is visible.
- *
- * Note: Notational guide:
- * The first three vertices of face #this_set are face[this_set][0], face[this_set][1], and
- * face[this_set][2]. Compute components as:
- * Ax = v[ face[this_set][2] ][X] - v[ face[this_set][1] ][X] ;
- * Bx = v[ face[this_set][0] ][X] - v[ face[this_set][1] ][X] ;
- * The fact that the vertex coordinates are relative to the center of rotation washes out
- * because of the difference is taken.
- */
-
- for ( i=0;i<NMBR_FACES;++i)
- { v0= face[i][0]; v1= face[i][1]; v2= face[i][2];
- ax= p_vx[v2] - p_vx[v1]; bx= p_vx[v0] - p_vx[v1];
- ay= p_vy[v2] - p_vy[v1]; by= p_vy[v0] - p_vy[v1];
-
- /* Condition a flag 'visible[this_set][i]' to indicate visible faces. */
- visible[this_set][i]= (ax*by- ay*bx)<0 ?1 :0;
- }
-
- /* Compute the face polygons. Note that restriction to convex solids means a face
- * either is visible or it is not; no partial visibility because of shadowing.
- */
- for( i=0; i<NMBR_FACES; ++i)
- { face_poly[this_set][i] = openpoly();
- moveto( p_vx[ face[i][0] ], p_vy[ face[i][0] ] );
- for(j=1;j<NMBR_VERTICES;++j)
- { lineto( p_vx[ face[i][j] ], p_vy[ face[i][j] ] );
- if( face[i][j] == face[i][0] ) break;
- }
- closepoly();
- }
-
- /* Display computational progress as a countdown */
- setrect(&count_rect,200,175,250,205);
- eraserect(&count_rect);
- moveto(210,200);
- i=(NMBR_SETS-this_set);
- if (i>99) drawchar(48+i/100); else drawchar(' ');
- if (i>99) { i%=100; drawchar(48+i/10); i%=10 ; }
- else{ if (i>9) {drawchar(48+i/10); i%=10; } else drawchar(' '); }
- drawchar(48+i);
- }
-
- /* Draw 'option bar' */
- textsize(12);
- setrect(&option_rect,0,0,76,20); framerect(&option_rect);
- moveto(5,16); drawchar('Q'); moveto(0,15); line(0,15);
- moveto(20,16); drawchar('X'); moveto(0,30); line(0,15);
- moveto(35,16); drawchar('Y'); moveto(0,45); line(0,15);
- moveto(50,16); drawchar('Z'); moveto(0,60); line(0,15);
- moveto(65,16); drawchar('F');
-
- return;
- }
-
- void rotate_object()
- { register int i;
- static int set=0;
- register polyhandle *poly;
-
- poly = &face_poly[set][0];
-
- eraserect(&display_rect);
- if ( drawflag == SOLID_LIGHT)
- { for(i=0;i<NMBR_FACES;++i)
- { if( visible[set][i] ==1)
- { if( light[set][i] ==1) fillpoly( *(poly+i),<gray);
- framepoly( *(poly+i));
- }
- }
- }
- else if ( drawflag == SOLID)
- { for(i=0;i<NMBR_FACES;++i)
- if ( visible[set][i] ==1) framepoly(*(poly+i));
- }
- else for(i=0;i<NMBR_FACES;++i) framepoly(*(poly+i));
-
- if( ++set == NMBR_SETS ) set=0; /* Update the rotation angle */
-
- return;
- }